Skip to content

Flexbox balance: bound memory/CPU via checkpointed readback and a state budget - #1130

Open
nicoburns wants to merge 1 commit into
mainfrom
devin/1787181956-balance-memory
Open

Flexbox balance: bound memory/CPU via checkpointed readback and a state budget#1130
nicoburns wants to merge 1 commit into
mainfrom
devin/1787181956-balance-memory

Conversation

@nicoburns

Copy link
Copy Markdown
Member

Objective

Follow-up to #1105 (flex-wrap: balance). The balancing DP stored a full (line_count - 1) * item_count table of u32 chosen ends purely for readback, so near-equal author-controlled item/line counts (e.g. 10,000 items with flex-line-count: 9999) could allocate hundreds of MiB and burn seconds of CPU inside layout. This PR bounds both memory and CPU without changing behavior for normal inputs.

Changes

  1. Checkpointed readback (exact, same results). The forward pass no longer stores every row's chosen ends. Rows are grouped into blocks of block_len ≈ sqrt(row_count); the forward pass keeps the score row entering each block (checkpoint) plus the chosen ends of the current block only, and readback recomputes one block at a time from its checkpoint (the final block's ends are still live from the forward pass, so it isn't recomputed). Row computation is factored into Dp::compute_row so the forward pass and readback recompute rows identically — the objective (sum of squared errors), feasibility constraints, zero-sized-item rule, and largest-end/earliest-line tie-break are all byte-for-byte the same DP; only the storage strategy changed.

    • Memory: O(item_count * sqrt(line_count)) instead of O(item_count * line_count) (10,000/9,999 drops from ~381 MiB of ends to ~1 MiB of checkpoints + ends).
    • Time: at most 2x the forward pass (each row is computed at most twice), still O(line_count * item_count * log(item_count)).
    • Allocation sizes are computed with checked_mul.
  2. State budget with a deterministic fallback. The exact DP solves line_count * item_count states; both factors are author-controlled (flex-line-count is a u16, item count unbounded), so the exact solve is limited to a centralized, test-parameterizable budget (EXACT_BUDGET = 1 << 22 states, comfortably above anything non-pathological; ~all real content stays exact). Beyond it, proportional_line_item_counts places each line break at the feasible position nearest an equal division of the total size in O(item_count log item_count) time and O(item_count) memory: exactly the selected line count, every line within the limit (unless a lone overflowing item), zero-sized items glued to the preceding line where the limit and remaining lines allow. Compatibility tradeoff: over-budget divisions are near-balanced but not the exact squared-error minimum nor the exact tie-break; this only affects inputs that previously took ~seconds/hundreds of MiB.

  3. Comment fixes. collect_balanced_flex_lines and balanced_line_item_counts claimed the algorithm makes "the largest line as small as possible"; the implemented (and spec) objective is minimizing the sum of squared errors (equivalently, sum of squared line sizes at fixed line count). Docs now say so, and the asymptotics/memory notes are updated.

Tests / benchmarks

  • matches_naive_dp_many_lines: randomized 80–160-item cases with near-equal line counts (many checkpoint blocks), including zeros/ties straddling recomputed block boundaries, checked against the naive O(k·n²) oracle.
  • exact_within_budget_matches_unbounded: the default budget produces identical output to an unlimited budget for in-budget inputs.
  • over_budget_fallback_is_valid: randomized inputs forced through the fallback (budget 0) keep exactly the exact algorithm's line count with all-valid lines.
  • pathological_line_counts_stay_bounded: 10,000 items at 5,000 and 9,999 lines complete instantly.
  • New flex-wrap: balance criterion benchmark group in benches/benches/flexbox.rs:

Context

  • no_std/alloc compatibility preserved (core only; verified with --no-default-features --features flexbox,flexbox_balance,alloc), flexbox_balance gate unchanged, MSRV 1.71 respected (no div_ceil/is_none_or/f64::sqrt).
  • Checks run: cargo fmt --all, cargo test --workspace (all green), cargo clippy --workspace -- -D warnings (clean; --all-features has a pre-existing strict_provenance MSRV lint on main), benches workspace clippy, and the new benchmark. Not run: gentest regeneration (no fixture changes) and --all-features clippy -D warnings due to the pre-existing warning.
  • RELEASES.md not updated: flex-wrap: balance is unreleased (Implement flex-wrap: balance (CSS Flexbox Level 2) #1105 post-0.9), so this is a fix to an unreleased feature.

Feedback wanted

Whether EXACT_BUDGET = 1 << 22 states is the right threshold — it's centralized and easy to tune.

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/747d1b0632c94205bab965174ba0a777
Requested by: @nicoburns

@staging-devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant